﻿// INGENIOUS FRAMEWORK - Semantic Web Development Simplified
// Copyright (C) 2012 Linked Data Tools

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections;
using System.Threading;

using MongoDB.Bson;
using MongoDB.Driver.Builders;
using MongoDB.Bson.Serialization.Attributes;

using Ingenious.Owl;
using Ingenious.GraphStore;
using Ingenious.Core;
using Ingenious.Utility;

using Caliburn.Micro;

namespace Ingenious.Graph
{
    #region Supporting Classes
    public enum FetchMode
    {
        Synchronous,
        Asynchronous,
    }    
    #endregion

    /// List types (delay loaded), value types (not delay loaded), other reference types (delay loaded)
    /// Lists & other reference types are serialized in the same way
    /// One dynamic list of properties, the other a list of objects
    /// These dynamic properties and list items are serialized in the same way

    // - Nodes always have URI based IDs
    // - These are persisted in the triple store so can be synchronized
    // - Triples have a subject ID which is always a URI, but a persistence ID determined by the storage provider
    // - Nodes have a unique URI, but these URIs are not unique for properties of the same node
    // - Persistence ID is unique at the property level

    // TODO: Need to generate a URI from an expression in the form Expression<Action<string>> where the string
    // is the property name. This allows dynamic resolution or conversion of property names -> uris and vice-version

    // Note: Implements IEnumerable<object> for list data binding purposes

    /// <summary>
    /// Node methods that do not require generic argument
    /// </summary>
    public class Node : DynamicObject
    {
        protected static readonly Dictionary<int, NamespaceUriScope> s_currentNamespaceUriScopesByThreadId = new Dictionary<int, NamespaceUriScope>();
        protected static readonly object s_namespaceUriScopesLock = new object();

        #region URI prefix scopes
        public static void RegisterNamespaceScope(NamespaceUriScope uriPrefixScope)
        {
            lock (s_namespaceUriScopesLock)
            {
                if (!s_currentNamespaceUriScopesByThreadId.ContainsKey(Thread.CurrentThread.ManagedThreadId))
                {
                    s_currentNamespaceUriScopesByThreadId[Thread.CurrentThread.ManagedThreadId] = uriPrefixScope;
                }
                else
                {
                    throw new NodeException("Recursive URI prefix scopes are not permitted");
                }
            }
        }

        public static void UnregisterUriPrefixScope(NamespaceUriScope uriPrefixScope)
        {
            lock (s_namespaceUriScopesLock)
            {
                if (s_currentNamespaceUriScopesByThreadId.ContainsKey(Thread.CurrentThread.ManagedThreadId))
                {
                    s_currentNamespaceUriScopesByThreadId.Remove(Thread.CurrentThread.ManagedThreadId);
                }
            }
        }
        #endregion
    }

    /// <summary>
    /// Node
    /// 
    /// A dynamic wrapper for a POCO class that allows properties to be defined at runtime.
    /// Implements delay-loading and client transaction functionality.
    /// </summary>
    public class Node<TTripleId> : Node, INotifyPropertyChanged, INotifyPropertyChanging, IEnumerable<NameValuePair<TTripleId>>, INode<TTripleId>
    {
        #region Supporting Classes
        private class CompiledDelegates
        {
            public Func<object, object> GetAccessor { get; set; }
            public Action<object, object> SetAccessor { get; set; }
        }

        private class StaticPropertyInfoCache : NodePropertyInfo<TTripleId>
        {
            public CompiledDelegates CompiledDelegates { get; set; }
        }

        private class PropertyChangeScope : IDisposable
        {
            private readonly Node<TTripleId> m_parent;
            private readonly string m_propertyName;

            #region Object Lifetime
            public PropertyChangeScope(Node<TTripleId> parent, string propertyName)
            {
                if (parent == null)
                {
                    throw new ArgumentNullException("parent");
                }

                if (propertyName == null)
                {
                    throw new ArgumentNullException("propertyName");
                }

                m_parent = parent;
                m_propertyName = propertyName;

                m_parent.OnPropertyChanging(m_propertyName);
            }

            public void Dispose()
            {
                m_parent.OnPropertyChanged(m_propertyName);
            }
            #endregion
        }
        #endregion

        private Uri m_subjectUri;

        private readonly object m_wrappedEntity;
        private Type m_nodeType;

        private readonly Graph<TTripleId> m_graph;
        private readonly FetchMode m_fetchMode = FetchMode.Asynchronous;
                
        private readonly object m_owlAttributeLock = new object();

        // Property values are instance specific
        private readonly IDictionary<string, NodePropertyInfo<TTripleId>> m_dynamicPropertyInfos = new Dictionary<string, NodePropertyInfo<TTripleId>>();
        //private readonly IDictionary<string, object> m_dynamicPropertyValues = new Dictionary<string, object>();
        private readonly object m_dynamicPropertyLock = new object();
        private bool m_dynamicPropertyInfosLoaded = false;

        // Property Info cache is global to the entity type
        private static readonly IDictionary<Type, IDictionary<string, StaticPropertyInfoCache>> s_staticPropertyDelegateCache = new Dictionary<Type, IDictionary<string, StaticPropertyInfoCache>>();
        private static readonly IDictionary<string, object> s_staticPropertyValues = new Dictionary<string, object>();
        private static readonly object s_staticPropertyLock = new object();
       
        #region Object Lifetime        
        /// <summary>
        /// POCO (Plain Old CLR Object) based constructor. Creates a new
        /// dynamic entity based upon the supplied POCO class, taking into
        /// account any LinqToOwl attributes given on the class.
        /// </summary>
        /// <param name="subjectUri">Subject URI of predicates contained by the node</param>
        /// <param name="graph">The graph containing the node</param>
        /// <param name="entity">POCO instance</param>
        /// <param name="fetchMode">Fetch mode (synchronous or asynchronous)</param>
        public Node(Uri subjectUri, Graph<TTripleId> graph, object entity, FetchMode fetchMode)
            : this(subjectUri, graph, entity)
        {
            m_fetchMode = fetchMode;
        }

        /// <summary>
        /// POCO (Plain Old CLR Object) based constructor. Creates a new
        /// dynamic entity based upon the supplied POCO class according to
        /// the default fetch mode, including those attributed with LinqToOwl
        /// attributes.
        /// </summary>
        /// <param name="entity">POCO instance</param>
        /// <param name="primaryEntity">The parent primary entity</param>
        public Node(Uri subjectUri, Graph<TTripleId> graph, object entity)
            : this(subjectUri, graph)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
                        
            m_wrappedEntity = entity;

            // Cache the wrapped entity property information
            // so we don't have to construct it again
            RegisterType(entity);
        }

        /// <summary>
        /// LinqToOWL based constructor. Takes a LinqToOWL class type and creates
        /// a dynamic entity with the OWL class characteristics.
        /// </summary>
        /// <param name="classType">OWL class type</param>
        /// <param name="primaryEntity">Parent primary entity</param>
        /// <param name="fetchMode">The fetch mode (synchronous or asynchronous)</param>
        public Node(Uri subjectUri, Graph<TTripleId> graph, Type classType, FetchMode fetchMode)
            : this(subjectUri, graph, fetchMode)
        {
            CreateClassInstance(classType);
        }

        /// <summary>
        /// Dynamic entity constructor
        /// </summary>
        /// <param name="primaryEntity">The parent primary entity</param>
        /// <param name="fetchMode">The fetch mode (asynchronous or synchronous)</param>
        public Node(Uri subjectUri, Graph<TTripleId> graph, FetchMode fetchMode)
            : this(subjectUri, graph)
        {
            m_fetchMode = fetchMode;
        }

        /// <summary>
        /// Blank node constructor
        /// </summary>
        public Node(Uri subjectUri, Graph<TTripleId> graph)
        {
            if (subjectUri == null)
            {
                throw new ArgumentNullException("id");
            }

            if (graph == null)
            {
                throw new ArgumentNullException("graph");
            }

            m_subjectUri = subjectUri;
            m_graph = graph;
        }
        #endregion

        #region Type Registration Cache
        private void ProcessLinqToOwlCharacteristics()
        {

        }

        private void RegisterType(object entity)
        {
            // Reflect over and cache the static properties on the entity,
            // turning them into dynamically accessible properties. When these
            // properties are set on a node, they are set on the wrapped
            // entity object itself rather than in the dynamically set
            // properties on the node.

            // Also takes into account any LinqToOwl attributes on the class type

            // Note: On first construction, the type is created and populated
            lock (s_staticPropertyLock)
            {
                m_nodeType = entity.GetType();

                if (!s_staticPropertyDelegateCache.ContainsKey(m_nodeType))
                {
                    s_staticPropertyDelegateCache[m_nodeType] = new Dictionary<string, StaticPropertyInfoCache>();
                    PropertyInfo[] propertyInfos = m_nodeType.GetProperties(BindingFlags.Instance | BindingFlags.Public);

                    foreach (PropertyInfo pi in propertyInfos)
                    {
                        StaticPropertyInfoCache staticPropertyInfoCache = new StaticPropertyInfoCache();
                        staticPropertyInfoCache.CompiledDelegates = new CompiledDelegates();

                        staticPropertyInfoCache.CompiledDelegates.GetAccessor = ReflectHelper.BuildGetAccessor(pi.GetGetMethod());
                        staticPropertyInfoCache.CompiledDelegates.SetAccessor = ReflectHelper.BuildSetAccessor(pi.GetSetMethod());
                        staticPropertyInfoCache.ItemWrappingInfo = TypeHelper.GetItemWrappingInfo(pi.PropertyType);

                        s_staticPropertyDelegateCache[m_nodeType][pi.Name] = staticPropertyInfoCache;
                    }
                }
            }

            // If the POCO is a LinqToOWL class, then initialise the OWL characteristics
            ProcessLinqToOwlCharacteristics();
        }
        
        /// <summary>
        /// Used to perform e.g. serialization specific actions for a newly
        /// registered type
        /// </summary>
        protected virtual void PostRegisterActions()
        {
            // Default: do nothing
        }
        #endregion  

        /// <summary>
        /// Creates a dynamic class instance of a LinqToOWL class
        /// </summary>
        /// <param name="classType">The LinqToOWL class type</param>
        /// <returns>The dynamic entity instance</returns>
        private void CreateClassInstance(Type classType)
        {
            List<ClassAttribute> classAttributes = new List<ClassAttribute>(classType.GetCustomAttributes(false).OfType<ClassAttribute>());

            if (classAttributes.Count == 0)
            {
                throw new ArgumentException("classType must be a LinqToOWL class type", "classType");
            }

            ClassAttribute classAttribute = classAttributes[0];

            // Interrogate the properties of this class type to create
            // the dynamic class instance
            //RegisterClassInstance(classAttribute.About.ToString());

            // TODO: What is the different between 'about' and 'id', and can
            // 'about' be formed from 'id' and ontology parent?

            // TODO: Get this information from a property cache if we have
            // created an instance of this type already

            PropertyInfo[] pis = classType.GetProperties();

            foreach (PropertyInfo pi in pis)
            {
                object[] customAttributes = pi.GetCustomAttributes(false);

                foreach (object customAttribute in customAttributes)
                {
                    //if (customAttribute is DatatypePropertyAttribute)
                    //{
                    //    DatatypePropertyAttribute datatypePropertyAttribute = (DatatypePropertyAttribute)customAttribute;

                    //    // Set the property in the dictionary with an initial default value
                    //    // according to the property type
                    //    InternalSetPropertyValue(pi.Name, pi.PropertyType, GetDefaultValue(pi.PropertyType));

                    //    // Run-time register the data type property instance
                    //    RegisterDatatypePropertyInstance(pi.Name, datatypePropertyAttribute.About.ToString());
                    //}
                    //else if (customAttribute is ObjectPropertyAttribute)
                    //{
                    //    ObjectPropertyAttribute objectPropertyAttribute = (ObjectPropertyAttribute)customAttribute;

                    //    // Set the property in the dictionary

                    //    // Run-time register the object property instance
                    //    RegisterObjectPropertyInstance(pi.Name, objectPropertyAttribute.About.ToString());
                    //}
                }
            }
        }

        private void LoadPropertyInfos()
        {
            lock (m_dynamicPropertyLock)
            {
                if (!m_dynamicPropertyInfosLoaded)
                {
                    // Note: Loads the property infos so that we can detect new properties or cross-reference
                    // the triple ID from the property name
                    IList<Tuple<string, NodePropertyInfo<TTripleId>>> nodePropertyInfos = Graph.Proxy.GraphStore.GetPropertyInfosForSubjectUri(Graph, m_subjectUri);
                    
                    foreach (Tuple<string, NodePropertyInfo<TTripleId>> nodePropertyInfo in nodePropertyInfos)
                    {
                        m_dynamicPropertyInfos.Add(nodePropertyInfo.Item1, nodePropertyInfo.Item2);
                    }

                    m_dynamicPropertyInfosLoaded = true;
                }
            }
        }

        #region Object Overrides

        /// <summary>
        /// Access a node predicate via property name index
        /// </summary>
        public dynamic this[string propertyName]
        {
            get
            {
                object value = null;
                InternalGetPropertyValue(ResolveNamespaceUriString() + propertyName, out value);

                return value;
            }

            set
            {
                InternalTrySetPropertyValue(ResolveNamespaceUriString() + propertyName, value.GetType(), value);
            }
        }

        #endregion

        #region Triple Serialization/Deserialization
        /// <summary>
        /// Serialises this node to a list of RDF triples
        /// </summary>
        /// <returns></returns>
        public List<Triple<TTripleId>> ToTriples()
        {
            List<Triple<TTripleId>> triples = new List<Triple<TTripleId>>();

            foreach (NameValuePair<TTripleId> predicateNameValuePair in this)
            {
                Uri predicateUri = null;
                Uri.TryCreate(predicateNameValuePair.Name, UriKind.Absolute, out predicateUri);

                if (predicateNameValuePair.IsLiteral)
                {
                    triples.Add(new Triple<TTripleId>(Subject, predicateUri, predicateNameValuePair.Value));
                }
                else
                {
                    triples.Add(new Triple<TTripleId>(Subject, predicateUri, ((INode<TTripleId>)predicateNameValuePair.Value).Subject));
                }
            }

            return triples;
        }
        
        #endregion        

        #region Property Setting And Retrieval

        /// <summary>
        /// Set the property value on the node
        /// </summary>
        /// <param name="resolvedPropertyName">The full predicate URI of the property</param>
        /// <param name="propertyType">The type of the property</param>
        /// <param name="value">The property value</param>
        public void SetPropertyValue(string resolvedPropertyName, Type propertyType, object value)
        {
            if (resolvedPropertyName == null)
            {
                throw new ArgumentNullException("resolvedPropertyName");
            }

            InternalTrySetPropertyValue(resolvedPropertyName, propertyType, value);
        }

        /// <summary>
        /// Get the property value via its full predicate URI
        /// </summary>
        /// <param name="resolvedPropertyName">The full predicate URI of the property</param>
        /// <param name="value">The property value</param>
        public void GetPropertyValue(string resolvedPropertyName, out object value)
        {
            if (resolvedPropertyName == null)
            {
                throw new ArgumentNullException("resolvedPropertyName");
            }

            InternalGetPropertyValue(resolvedPropertyName, out value);
        }

        // Raises property changes on the sync context thread
        private void DispatchPropertyChange(string propertyName, object currentValue, object newValue)
        {
            // If the value has changed (e.g. it has been loaded) then
            // raise a property change in the sync context thread
            if (currentValue != null)
            {
                if (newValue != null)
                {
                    // Use the overridable equality check
                    if (!currentValue.Equals(newValue))
                    {
                        // Note: This is the application SC thread, not the Ingenious
                        // framework SC thread
                        m_graph.Proxy.SynchronizationContext.Post((o) =>
                        {
                            OnPropertyChanged(propertyName);
                        }, null);
                    }
                }
                else
                {
                    // Note: This is the application SC thread, not the Ingenious
                    // framework SC thread
                    m_graph.Proxy.SynchronizationContext.Post((o) =>
                    {
                        OnPropertyChanged(propertyName);
                    }, null);
                }
            }
            else
            {
                if (newValue != null)
                {
                    // Note: This is the application SC thread, not the Ingenious
                    // framework SC thread
                    m_graph.Proxy.SynchronizationContext.Post((o) =>
                    {
                        OnPropertyChanged(propertyName);
                    }, null);
                }
            }
        }
        
        #region Set Node Property
        private void OnSetPropertyTripleLoaded(string resolvedPropertyName, GraphScope<TTripleId> graphScope, Triple<TTripleId> triple)
        {            
            lock (m_dynamicPropertyLock)
            {
                // Store the value (TODO: Shouldn't this be grabbed from the cache?)
                m_dynamicPropertyInfos[resolvedPropertyName].Value = triple.Value;

                if (m_fetchMode == FetchMode.Asynchronous)
                {
                    TripleChange<TTripleId> tripleChange = null;

                    // We've received the item - now to the equality check and issue the change update
                    // if it is required
                    if (graphScope.TryGetPendingChange(triple.ID, out tripleChange))
                    {
                        bool propertyChanged = !object.Equals(triple.Value, tripleChange.Triple.Value);

                        if (propertyChanged)
                        {
                            // Enumerate the change unit
                            graphScope.EnumerateChange(tripleChange);

                            // Immediately raise a property change on the SC thread
                            DispatchPropertyChange(resolvedPropertyName, triple.Value, tripleChange.Triple.Value);
                        }
                    }
                    else
                    {
                        // Should not happen, this should have been registered
                        throw new InvalidOperationException("Cannot find matching pending change for triple with ID '" + triple.ID.ToString() + "'");
                    }
                }
            }
        }

        private bool TrySetStaticPropertyValue(GraphScope<TTripleId> graphScope, string resolvedPropertyName, object value)
        {
            // Check whether this is a static property defined on the wrapped entity
            lock (s_staticPropertyLock)
            {
                if (m_nodeType != null)
                {
                    if (s_staticPropertyDelegateCache[m_nodeType].ContainsKey(resolvedPropertyName))
                    {
                        TTripleId id = s_staticPropertyDelegateCache[m_nodeType][resolvedPropertyName].ID;
                        object currentValue = s_staticPropertyDelegateCache[m_nodeType][resolvedPropertyName].CompiledDelegates.GetAccessor(m_wrappedEntity);

                        if (!m_graph.Proxy.GraphStore.IsLoaded(m_graph.Name, id))
                        {
                            // We must load the property first. If we are in async mode, do
                            // this asynchronously and then do an asynchronous set
                            if (graphScope.FetchMode == FetchMode.Synchronous)
                            {
                                // Note: In synchronous mode, delay loading still happens - it is just that
                                // the method to retrieve the property only returns when the property
                                // has been retrieved from the data store

                            }
                            else
                            {
                                // Asynchronously get the property, only adding to the
                                // transaction change list if the value has actually changed

                                // We signal/fire an event when the property is actually retrieved before continuing
                                // to process the property change

                            }
                        }

                        if (!currentValue.Equals(value))
                        {
                            // This is a static property on the wrapped entity. Get it using the compiled expression delegate.
                            s_staticPropertyDelegateCache[m_nodeType][resolvedPropertyName].CompiledDelegates.SetAccessor.Invoke(m_wrappedEntity, value);
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private bool TrySetDynamicPropertyValue(GraphScope<TTripleId> graphScope, string resolvedPropertyName, Type propertyType, object value)
        {
            // Now search the dynamic property store
            lock (m_dynamicPropertyLock)
            {
                // Make sure the node property infos are loaded
                // Note that this will also immediately load value type properties
                // as these are never delay-loaded
                LoadPropertyInfos();

                bool newProperty = false;
                bool propertyChanged = false;
                bool propertyInitiallyLoaded = false;

                NodePropertyInfo<TTripleId> nodePropertyInfo = null;
                Triple<TTripleId> triple = null;

                // Is this a new property that we have not seen before?
                if (!m_dynamicPropertyInfos.TryGetValue(resolvedPropertyName, out nodePropertyInfo))
                {
                    // It's a new property, so add it to the node property info list
                    nodePropertyInfo = new NodePropertyInfo<TTripleId>();
                    nodePropertyInfo.ID = Graph.Proxy.GraphStore.CreateNewTripleId();
                    nodePropertyInfo.ItemWrappingInfo = TypeHelper.GetItemWrappingInfo(propertyType);

                    m_dynamicPropertyInfos[resolvedPropertyName] = nodePropertyInfo;

                    // Indicate it is a new property
                    newProperty = true;
                }
                else
                {
                    TTripleId tripleId = m_dynamicPropertyInfos[resolvedPropertyName].ID;

                    if(!m_graph.Proxy.GraphStore.IsLoaded(m_graph.Name, tripleId))
                    {
                        // In this pass, the property hasn't been loaded, so we will load it now
                        propertyInitiallyLoaded = false;

                        // Load the triple associated with this triple ID to obtain the current value
                        triple = m_graph.Proxy.GraphStore.GetTripleById(graphScope,
                            m_graph.Name,
                            m_dynamicPropertyInfos[resolvedPropertyName].ID,
                            resolvedPropertyName, m_fetchMode,
                            new PropertyLoadedDelegate<TTripleId>(OnSetPropertyTripleLoaded));
                    }
                }

                if (newProperty || propertyChanged || !propertyInitiallyLoaded)
                {
                    // Get the value type from the binder to handle nullable types
                    // where value is null and we can't determine the type from the value
                    ItemType itemType = m_dynamicPropertyInfos[resolvedPropertyName].ItemWrappingInfo.ItemType;
                    Triple<TTripleId> changeTriple = null;

                    if (TypeHelper.IsValueItemType(itemType))
                    {
                        // Value types are not delay loaded so we can enumerate the change immediately
                        if (newProperty || propertyChanged)
                        {
                            // There has been a change (add property or modify current value)
                            m_dynamicPropertyInfos[resolvedPropertyName].LiteralValue = value;

                            // Literal value triple
                            changeTriple = new Triple<TTripleId>(m_subjectUri, new Uri(resolvedPropertyName), value);
                            changeTriple.ID = m_dynamicPropertyInfos[resolvedPropertyName].ID;

                            TripleChange<TTripleId> tripleChange = new TripleChange<TTripleId>(Graph, changeTriple, newProperty ? TripleChangeType.Add : TripleChangeType.Modify);

                            // Enumerate the change on the transaction
                            graphScope.EnumerateChange(tripleChange);
                        }
                    }
                    else if (TypeHelper.IsReferenceItemType(itemType))
                    {
                        // Reference types are delay loaded
                        if (newProperty)
                        {
                            // It's a new property, so we don't have to delay load. Whether we are in asynchronous or synchronous
                            // mode, we know we can register a write triple change now.
                            Triple<TTripleId> newTriple = new Triple<TTripleId>(m_subjectUri, new Uri(resolvedPropertyName), ((INode<TTripleId>)value).Subject);
                            newTriple.ID = m_dynamicPropertyInfos[resolvedPropertyName].ID;

                            // Enumerate the change
                            graphScope.EnumerateChange(new TripleChange<TTripleId>(Graph, newTriple, TripleChangeType.Add));

                            // Set the new property
                            m_dynamicPropertyInfos[resolvedPropertyName].Value = value;

                            // And immediately raise a property changed on the application SC thread
                            DispatchPropertyChange(resolvedPropertyName, null, value);
                        }
                        else
                        {
                            // If this is the first time it has been loaded
                            if (!propertyInitiallyLoaded)
                            {
                                if (m_fetchMode == FetchMode.Asynchronous)
                                {
                                    // We are in asynchronous fetch mode, and the property hasn't yet
                                    // been loaded. So, add this to the list of pending changes whilst
                                    // we work out whether the value of the property has changed or not

                                    // TODO: Use type mapping to handle reference types other than INode<>
                                    if (value is INode<TTripleId>)
                                    {
                                        TripleChange<TTripleId> tripleChange = new TripleChange<TTripleId>(Graph, new Triple<TTripleId>(m_subjectUri, new Uri(resolvedPropertyName), ((INode<TTripleId>)value).Subject), TripleChangeType.Modify);
                                        graphScope.RegisterPendingChange(tripleChange);
                                    }
                                    else
                                    {
                                        throw new NotImplementedException();
                                    }
                                }
                            }

                            if (m_graph.Proxy.GraphStore.IsLoaded(m_graph.Name, triple.ID))
                            {
                                // Whether in asynchronous or synchronous mode, the property has loaded, so check if
                                // it has changed
                                if (triple.IsLiteral)
                                {
                                    propertyChanged = !object.Equals(triple.Value, value);
                                }
                                else
                                {
                                    propertyChanged = !object.Equals(triple.Object, ((INode<TTripleId>)(value)).Subject);
                                }

                                // Note: Whether the property has changed or not we still set the value
                                // so that on first setting an unloaded property the initial value appears
                                m_dynamicPropertyInfos[resolvedPropertyName].Value = value;

                                if (propertyChanged)
                                {
                                    // Reference value - reference the subject URI of the object
                                    changeTriple = new Triple<TTripleId>(m_subjectUri, new Uri(resolvedPropertyName), ((INode<TTripleId>)value).Subject);

                                    // Enumerate the change unit
                                    graphScope.EnumerateChange(new TripleChange<TTripleId>(Graph, changeTriple, TripleChangeType.Modify));

                                    // Raise property change on the application SC thread
                                    DispatchPropertyChange(resolvedPropertyName, triple.Value, value);
                                }
                            }
                        }
                    }
                    else
                    {
                        // Should not happen
                        throw new InvalidOperationException("Unknown item type '" + itemType + "'");
                    }
                }
            }

            return true;
        }

        private bool InternalTrySetPropertyValue(string resolvedPropertyName, Type propertyType, object value)
        {
            GraphScope<TTripleId> graphScope = null;
            GraphScope<TTripleId> implicitGraphScope = null;

            try
            {
                // First verify that this thread has an open write transaction scope            
                // before we allow the setting of a property
                if (!m_graph.Proxy.HasOpenWriteGraphScope(Thread.CurrentThread.ManagedThreadId, out graphScope))
                {
                    // This will throw if the caller already has a read transaction open
                    graphScope = new GraphScope<TTripleId>(m_graph, GraphLock.Write, m_graph.Proxy.ImplicitFetchMode);
                    implicitGraphScope = graphScope;
                }

                // Try and set as a static property, if present
                if (!TrySetStaticPropertyValue(graphScope, resolvedPropertyName, value))
                {
                    // Try and set as a dynamic property, if present
                    TrySetDynamicPropertyValue(graphScope, resolvedPropertyName, propertyType, value);
                }
            }
            finally
            {
                if (implicitGraphScope != null)
                {
                    implicitGraphScope.Dispose();
                }
            }

            return true;
        }
        #endregion

        #region Get Node Property
        private void OnGetPropertyTripleLoaded(string propertyName, GraphScope<TTripleId> graphScope, Triple<TTripleId> triple)
        {
            lock (s_staticPropertyLock)
            {
                if (s_staticPropertyDelegateCache[m_nodeType].ContainsKey(propertyName))
                {
                    // Get the current value now that the property has returned
                    object currentValue = s_staticPropertyDelegateCache[m_nodeType][propertyName].CompiledDelegates.GetAccessor(propertyName);

                    if (currentValue != triple.Value)
                    {
                        DispatchPropertyChange(propertyName, currentValue, triple.Value);
                    }
                }
            }

            lock (m_dynamicPropertyLock)
            {
                if (m_dynamicPropertyInfos.ContainsKey(propertyName))
                {
                    // Get the current value now that the property has returned
                    object currentValue = m_dynamicPropertyInfos[propertyName];

                    // TODO: Resolve references here
                    DispatchPropertyChange(propertyName, currentValue, triple.Value);
                }
            }
        }
        
        private bool InternalGetPropertyValue(string resolvedPropertyName, out object result)
        {
            result = null;

            GraphScope<TTripleId> graphScope = null;
            GraphScope<TTripleId> implicitGraphScope = null;

            try
            {
                // Any transaction scope (read/upgrade/write) is acceptable for a read action
                if (!m_graph.Proxy.HasOpenTransaction(Thread.CurrentThread.ManagedThreadId, out graphScope))
                {
                    // Open an implicit read transaction that will be closed immediately
                    // on completing the read with the default proxy fetch mode
                    implicitGraphScope = new GraphScope<TTripleId>(m_graph, GraphLock.Read, m_graph.Proxy.ImplicitFetchMode);
                    graphScope = implicitGraphScope;
                }

                // First check whether this is a static property defined on the wrapped entity
                lock (s_staticPropertyLock)
                {
                    if (m_nodeType != null)
                    {
                        if (s_staticPropertyDelegateCache[m_nodeType].ContainsKey(resolvedPropertyName))
                        {
                            ItemType itemType = m_dynamicPropertyInfos[resolvedPropertyName].ItemWrappingInfo.ItemType;

                            // Whether it is delay loaded depends on the property item type
                            if (TypeHelper.IsValueItemType(itemType))
                            {
                                // Value types are not delay loaded and are therefore returned
                                // immediately in either synchronous or asynchronous mode
                                result = s_staticPropertyDelegateCache[m_nodeType][resolvedPropertyName].CompiledDelegates.GetAccessor.Invoke(m_wrappedEntity);
                            }
                            else if (TypeHelper.IsReferenceItemType(itemType))
                            {
                                // Reference item types (entities/entity lists) are delay loaded (sync or async)

                                // Get the ID of the delay loaded object from the info cache
                                TTripleId id = s_staticPropertyDelegateCache[m_nodeType][resolvedPropertyName].ID;

                                object currentValue = null;
                                //object currentValue = s_staticPropertyDelegateCache[m_nodeType][propertyName].Value;

                                if (graphScope.FetchMode == FetchMode.Synchronous)
                                {
                                    // In this case, we are returned the result synchronously
                                    // and we dispatch the property change immediately
                                    result = m_graph.Proxy.GraphStore.GetTripleById(graphScope, graphScope.Graph.Name, id, resolvedPropertyName, graphScope.FetchMode, null);
                                    DispatchPropertyChange(resolvedPropertyName, currentValue, result);
                                }
                                else
                                {
                                    // In async mode, we maintain the current value and only set it to the new
                                    // value on receiving the item loaded callback
                                    result = currentValue;
                                    m_graph.Proxy.GraphStore.GetTripleById(graphScope, graphScope.Graph.Name, id, resolvedPropertyName, FetchMode.Asynchronous, new PropertyLoadedDelegate<TTripleId>(OnGetPropertyTripleLoaded));
                                }
                            }
                            else
                            {
                                // Should not happen
                                throw new InvalidOperationException("Unknown item type '" + itemType + "'");
                            }
                        }
                    }
                }

                // Now search the dynamic property store
                lock (m_dynamicPropertyLock)
                {
                    LoadPropertyInfos();

                    NodePropertyInfo<TTripleId> nodePropertyInfo = null;

                    if (m_dynamicPropertyInfos.TryGetValue(resolvedPropertyName, out nodePropertyInfo))
                    {
                        // Not a new property - and shouldn't be if we are attempting to get it
                        ItemType itemType = nodePropertyInfo.ItemWrappingInfo.ItemType;

                        TTripleId id = nodePropertyInfo.ID;

                        // Whether it is delay loaded depends on the property item type
                        if (TypeHelper.IsValueItemType(itemType))
                        {
                            // Value types are not delay loaded and are therefore returned
                            // immediately in either synchronous or asynchronous mode
                            result = nodePropertyInfo.LiteralValue;
                        }
                        else if (TypeHelper.IsReferenceItemType(itemType))
                        {
                            // These item property types are delay loaded (sync or async)
                            if (graphScope.FetchMode == FetchMode.Synchronous)
                            {
                                Triple<TTripleId> triple = m_graph.Proxy.GraphStore.GetTripleById(graphScope, graphScope.Graph.Name, id, resolvedPropertyName, FetchMode.Synchronous, null);
                                result = triple.Value;

                                DispatchPropertyChange(resolvedPropertyName, nodePropertyInfo.Value, result);
                            }
                            else
                            {
                                // In async mode, we maintain the current value and only set it to the new
                                // value on receiving the item loaded callback
                                result = nodePropertyInfo.Value;
                                m_graph.Proxy.GraphStore.GetTripleById(graphScope, graphScope.Graph.Name, id, resolvedPropertyName, FetchMode.Asynchronous, new PropertyLoadedDelegate<TTripleId>(OnGetPropertyTripleLoaded));
                            }
                        }
                        else
                        {
                            // Should not happen
                            throw new InvalidOperationException("Unknown item type '" + itemType + "'");
                        }

                        return true;
                    }
                    else
                    {
                        throw new NodeException("Could not find property with name '" + resolvedPropertyName + "'. Set the property first if this is a new property.");
                    }
                }
            }
            finally
            {
                if (implicitGraphScope != null)
                {
                    implicitGraphScope.Dispose();
                }
            }

            // We didn't find a property match in either the wrapped POCO instance
            // or the entity's dynamic properties
            return false;
        }
        #endregion

        #endregion

        #region Runtime RDF properties, RDFS & OWL vocabulary

        /// <summary>
        /// Registers a node property as an instance of an OWL datatype property
        /// </summary>
        /// <param name="propertyName">The name of the property</param>
        /// <param name="id">The OWL ID stub</param>
        public void RegisterDatatypePropertyInstance(string propertyName, Uri id)
        {

        }

        /// <summary>
        /// Registers a node property as an instance of an OWL object property
        /// </summary>
        /// <param name="propertyName">The name of the property</param>
        /// <param name="id">The OWL ID stub</param>
        public void RegisterObjectPropertyInstance(string propertyName, Uri id)
        {

        }

        #endregion

        #region Overrides

        private NamespaceUriScope ResolveNamespaceUriScope()
        {
            NamespaceUriScope namespaceUriScope = null;

            // Obtain the current thread's Uri Prefix Scope
            lock (s_namespaceUriScopesLock)
            {
                if (!s_currentNamespaceUriScopesByThreadId.ContainsKey(Thread.CurrentThread.ManagedThreadId))
                {
                    throw new NodeException("No URI prefix scope defined. Get or set properties within a URI prefix scope.");
                }

                namespaceUriScope = s_currentNamespaceUriScopesByThreadId[Thread.CurrentThread.ManagedThreadId];
                Graph.NamespaceUris.Add(namespaceUriScope.NamespaceUri);
            }

            return namespaceUriScope;
        }

        private NamespaceUri ResolveNamespaceUri()
        {
            return ResolveNamespaceUriScope().NamespaceUri;
        }

        private string ResolveNamespaceUriString()
        {
            return ResolveNamespaceUriScope().NamespaceUriString;
        }

        #region Dynamic Object Overrides
        // For delay loading and for deferring to the wrapped entity properties
        // Note: This is a performance-critical method
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return InternalGetPropertyValue(ResolveNamespaceUriString() + binder.Name, out result);
        }
        
        // For setting/transactions
        // Note: This is a performance-critical method
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            return InternalTrySetPropertyValue(ResolveNamespaceUriString() + binder.Name, value.GetType(), value);
        }

        // For implementing 'base class' functionality for the plain old CLR object
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            return base.TryInvokeMember(binder, args, out result);
        }
        #endregion

        #endregion

        #region Property Changed
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangingEventHandler PropertyChanging;

        private void OnPropertyChanging(string propertyName)
        {
            if (PropertyChanging != null)
            {
                PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
            }
        }
        #endregion
        
        #region Dynamic Property Enumeration
        public IEnumerator<NameValuePair<TTripleId>> GetEnumerator()
        {
            return m_dynamicPropertyInfos.Select(kv => new NameValuePair<TTripleId>(this, kv)).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion

        #region Properties

        /// <summary>
        /// The subject URI (ID) of this node
        /// </summary>
        public Uri Subject
        {
            get { return m_subjectUri; }
        }

        /// <summary>
        /// The node representing the predicate (if resolvable)
        /// </summary>
        public Node<TTripleId> PredicateNode
        {
            get { return null; }
        }

        /// <summary>
        /// Convenience list of property names owned by this node
        /// </summary>
        public ICollection<string> PropertyNames
        {
            get
            {
                lock (m_dynamicPropertyLock)
                {
                    return m_dynamicPropertyInfos.Keys;
                }
            }
        }

        /// <summary>
        /// The graph to which this node belongs
        /// </summary>
        public Graph<TTripleId> Graph
        {
            get { return m_graph; }
        }
        #endregion
    }
}
